package cn.amossun.starter.common.crypto.rule;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.amossun.starter.common.enums.OutTypeEnum;
import cn.amossun.starter.common.properties.DataSecurityProperties;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;

/**
 * @description:
 * @author: Amos.Sun
 * @DateTime: 2020/03/10 17:25
 **/
public abstract class AbstractRule implements Cloneable {

    /**
     * 加解密对象
     * 无法继承cipher后使用Object.clone方案, 因Cipher构造方法为 protected Cipher() 以及Cipher.getInstance实例方法为final
     */
    //private Cipher cipher;

    /**
     * 字符集
     */
    private Charset characterSet;

    /**
     * 输出
     */
    private OutTypeEnum outType;

    private DataSecurityProperties properties;

    private int opmode;

    public DataSecurityProperties getProperties() {
        return properties;
    }

    public void setProperties(DataSecurityProperties properties) {
        this.properties = properties;
    }

    public int getOpmode() {
        return opmode;
    }

    public void setOpmode(int opmode) {
        this.opmode = opmode;
    }

    public List<String> getALGORITHMS() {
        return ALGORITHMS;
    }

    private final List<String> ALGORITHMS = new ArrayList<String>(){{
        add("AES");
        add("DES");
        add("RSA");
    }};

    public void setCharacterSet(Charset characterSet){
        this.characterSet = characterSet;
    }

    public void setOutType(OutTypeEnum outType){
        this.outType = outType;
    }

    /*public void setCipher(Cipher cipher) {
        this.cipher = cipher;
    }

    public Cipher getCipher() {
        return this.cipher;
    }*/

    public Charset getCharacterSet() {
        return this.characterSet;
    }

    public OutTypeEnum getOutType(){
        return this.outType;
    }

    public Cipher buildCipher() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException, UnsupportedEncodingException {
        return buildCipher(getProperties(), getOpmode());
    }

    public Cipher buildCipher(DataSecurityProperties properties, int opmode) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, UnsupportedEncodingException {
        List<String> algorithms = Arrays.asList(properties.getCipherModePadding().split("/"));
        if(CollectionUtil.isEmpty(algorithms) || algorithms.size() <= 1) {
            throw new NoSuchPaddingException("算法配置格式异常,请检查cipherModePadding.");
        }
        String algorithm = algorithms.stream().findFirst().get();
        if(!ALGORITHMS.contains(algorithm)) {
            throw new NoSuchAlgorithmException("算法配置异常,请检查cipherModePadding.");
        }

        byte[] cryptogramBytes = properties.getCryptogram().getBytes(getCharacterSet());
        SecretKeySpec secretKeySpec = new SecretKeySpec(cryptogramBytes, algorithm);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(properties.getOffset().getBytes(getCharacterSet()));
        
        Cipher cipher = Cipher.getInstance(properties.getCipherModePadding());
        cipher.init(opmode, secretKeySpec, ivParameterSpec);
        return cipher;
    }

    /**
     * 检查加密配置是否存在
     * @return
     */
    protected boolean decryptConfigExists() {
        if(StrUtil.isEmpty(getProperties().getDataBeforeSuffix()) || StrUtil.isEmpty(getProperties().getDataAfterSuffix())) {
            return false;
        }
        return true;
    }

    /**
     * 是否密文数据：根据加密前后缀检查
     * @param originalValue
     * @return
     */
    protected boolean isDecryptData(String originalValue) {
        //检查是否未配置加密特殊字符前后缀
        if(!decryptConfigExists()) {
            return false;
        }
        if(originalValue.startsWith(getProperties().getDataBeforeSuffix())
                && originalValue.endsWith(getProperties().getDataAfterSuffix())) {
            return true;
        }
        return false;
    }

}
